package com.uuah.server.component.service;

import java.beans.ExceptionListener;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.uuah.concurrent.WaitableBoolean;
import com.uuah.config.i18n.MessageFactory;
import com.uuah.exception.AbstractUuahException;
import com.uuah.exception.lifecycle.InitialisationException;
import com.uuah.server.api.IUuahContext;
import com.uuah.server.api.IUuahEvent;
import com.uuah.server.api.IUuahMessage;
import com.uuah.server.api.channel.IChannel;
import com.uuah.server.api.component.IComponent;
import com.uuah.server.api.component.model.IModel;
import com.uuah.server.api.component.service.IService;
import com.uuah.server.api.endpoint.IUuahEndpoint;
import com.uuah.server.api.transport.IMessageReceiver;
import com.uuah.server.channel.DefaultChannel;
import com.uuah.server.config.i18n.ServerMessages;
import com.uuah.server.context.notification.ServiceNotification;
import com.uuah.server.exception.AbstractUuahServerException;
import com.uuah.server.exception.model.ModelException;
import com.uuah.server.exception.service.ServiceException;
import com.uuah.server.exception.transformer.TransformerException;
import com.uuah.server.exception.transport.DispatchException;
import com.uuah.server.transport.AbstractConnector;
import com.uuah.server.transport.RequestContextHelper;

public abstract class AbstractService implements IService {
	protected transient Logger logger = LoggerFactory.getLogger(getClass());
	protected AtomicBoolean stopped = new AtomicBoolean(true);
	protected WaitableBoolean stopping = new WaitableBoolean(false);
	protected AtomicBoolean initialised = new AtomicBoolean(false);
	protected IModel model;
	protected WaitableBoolean paused = new WaitableBoolean(false);

	protected IUuahContext uuahContext;

	public static final String INITIAL_STATE_STOPPED = "stopped";
	public static final String INITIAL_STATE_STARTED = "started";
	public static final String INITIAL_STATE_PAUSED = "paused";

	protected ExceptionListener exceptionListener;

	protected String name;

	protected IChannel requestChannel = new DefaultChannel();

	protected String initialState = INITIAL_STATE_STARTED;

	private AtomicBoolean beyondInitialState = new AtomicBoolean(false);

	protected IComponent component;

	public AbstractService() {
		// nop
	}

	public final synchronized void initialise() throws InitialisationException {
		if (initialised.get()) {
			throw new InitialisationException(ServerMessages
					.objectAlreadyInitialised("Service '" + name + "'"), this);
		}

		component.setService(this);
		component.initialise();

		if (exceptionListener == null) {
			exceptionListener = getModel().getExceptionListener();
		}

		doInitialise();

		initialised.set(true);
		fireServiceNotification(ServiceNotification.SERVICE_INITIALISED);
	}

	protected void fireServiceNotification(int action) {
		uuahContext.fireNotification(new ServiceNotification(this, action));
	}

	public void forceStop() throws AbstractUuahException {
		if (!stopped.get()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Stopping Service");
			}
			stopping.set(true);
			fireServiceNotification(ServiceNotification.SERVICE_STOPPING);
			doForceStop();
			stopped.set(true);
			stopping.set(false);
			fireServiceNotification(ServiceNotification.SERVICE_STOPPED);
		}
	}

	public void stop() throws AbstractUuahException {
		if (!stopped.get()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Stopping Service");
			}
			stopping.set(true);
			fireServiceNotification(ServiceNotification.SERVICE_STOPPING);

			unregisterListeners();

			if (isPaused()) {
				resume();
			}

			doStop();

			component.stop();

			stopped.set(true);
			fireServiceNotification(ServiceNotification.SERVICE_STOPPED);
			if (logger.isInfoEnabled()) {
				logger.info("Uuah Service " + name
						+ " has been stopped successfully");
			}
		}
	}

	public void start() throws AbstractUuahException {
		if (!initialised.get()) {
			throw new IllegalStateException(
					"Cannot start an unitialised service.");
		}
		if (isStarted()) {
			if (logger.isInfoEnabled()) {
				logger.info("Service is already started: " + name);
			}
		} else {
			if (initialState.equals(AbstractService.INITIAL_STATE_STOPPED)) {
				if (logger.isInfoEnabled()) {
					logger.info("stopped");
				}
			}
			if (!beyondInitialState.get()
					&& initialState
							.equals(AbstractService.INITIAL_STATE_STOPPED)) {
				if (logger.isInfoEnabled()) {
					logger
							.info("Service "
									+ name
									+ " has not been started (initial state = 'stopped')");
				}
			} else if (!beyondInitialState.get()
					&& initialState
							.equals(AbstractService.INITIAL_STATE_PAUSED)) {
				start(true);
				if (logger.isInfoEnabled()) {
					logger
							.info("Service "
									+ name
									+ " has been started and paused (initial state = 'paused')");
				}
			} else {
				start(false);
				if (logger.isInfoEnabled()) {
					logger.info("Service " + name
							+ " has been started successfully");
				}
			}
			beyondInitialState.set(true);
		}
	}

	protected void start(boolean startPaused) throws AbstractUuahException {
		component.start();

		registerListeners();

		// �����������
		connectListeners();

		// Start (and pause) the service.
		if (stopped.get()) {
			stopped.set(false);
			paused.set(false);
			doStart();
		}
		fireServiceNotification(ServiceNotification.SERVICE_STARTED);
		if (startPaused) {
			pause();
		}

		startListeners();
	}

	public final void pause() throws AbstractUuahServerException {
		doPause();
		paused.set(true);
		fireServiceNotification(ServiceNotification.SERVICE_PAUSED);
		if (logger.isInfoEnabled()) {
			logger
					.info("Uuah Service " + name
							+ " has been paused successfully");
		}
	}

	public final void resume() throws AbstractUuahServerException {
		doResume();
		paused.set(false);
		fireServiceNotification(ServiceNotification.SERVICE_RESUMED);
		if (logger.isInfoEnabled()) {
			logger.info("Uuah Service " + name
					+ " has been resumed successfully");
		}
	}

	public boolean isPaused() {
		return paused.get();
	}

	protected void doPause() throws AbstractUuahServerException {
		// template method
	}

	protected void doResume() throws AbstractUuahServerException {
		// template method
	}

	public final void dispose() {
		try {
			if (!stopped.get()) {
				stop();
			}
		} catch (AbstractUuahException e) {
			logger.error("Failed to stop service: " + name, e);
		}
		doDispose();
		component.dispose();
		initialised.set(false);
		fireServiceNotification(ServiceNotification.SERVICE_DISPOSED);
	}

	public void dispatchEvent(IUuahEvent event)
			throws AbstractUuahServerException {
		if (stopping.get() || stopped.get()) {
			throw new ServiceException(ServerMessages.componentIsStopped(name),
					event.getMessage(), this);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Service: " + name
					+ " has received asynchronous event on: "
					+ event.getEndpoint().getEndpointURI());
		}

		doDispatch(event);
	}

	public IUuahMessage sendEvent(IUuahEvent event)
			throws AbstractUuahServerException {
		if (stopping.get() || stopped.get()) {
			throw new ServiceException(ServerMessages.componentIsStopped(name),
					event.getMessage(), this);
		}

		try {
			waitIfPaused(event);
		} catch (InterruptedException e) {
			throw new ServiceException(event.getMessage(), this, e);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Service: " + name
					+ " has received synchronous event on: "
					+ event.getEndpoint().getEndpointURI());
		}
		event = RequestContextHelper.unsafeSetEvent(event);
		return doSend(event);
	}

	protected void waitIfPaused(IUuahEvent event) throws InterruptedException {
		if (logger.isDebugEnabled() && paused.get()) {
			logger.debug("Service: " + name
					+ " is paused. Blocking call until resume is called");
		}
		paused.whenFalse(null);
	}

	protected void processAsyncResponse(IUuahMessage result, IUuahEvent event)
			throws DispatchException, TransformerException {
		result.applyTransformer(event.getEndpoint().getResponseTransformer());
		event.getEndpoint().getConnector().dispatch(result, event);
	}

	public String getName() {
		return name;
	}

	public String toString() {
		return getName();
	}

	public boolean isStopped() {
		return stopped.get();
	}

	public boolean isStopping() {
		return stopping.get();
	}

	protected void handleException(Exception e) {
		exceptionListener.exceptionThrown(e);
	}

	protected void doForceStop() throws AbstractUuahException {

	}

	protected void doStop() throws AbstractUuahException {

	}

	protected void doStart() throws AbstractUuahException {

	}

	protected void doDispose() {

	}

	protected void doInitialise() throws InitialisationException {

	}

	public boolean isStarted() {
		return !stopped.get();
	}

	protected abstract IUuahMessage doSend(IUuahEvent event)
			throws AbstractUuahServerException;

	protected abstract void doDispatch(IUuahEvent event)
			throws AbstractUuahServerException;

	protected void registerListeners() throws AbstractUuahServerException {
		IUuahEndpoint endpoint = getIncomingEndpoint();

		try {
			endpoint.getConnector().registerListener(this, endpoint);
		} catch (AbstractUuahServerException e) {
			throw e;
		} catch (Exception e) {
			throw new ModelException(ServerMessages.failedtoRegisterOnEndpoint(
					name, endpoint.getEndpointURI()), e);
		}

	}

	protected void unregisterListeners() throws AbstractUuahServerException {
		IUuahEndpoint endpoint = getIncomingEndpoint();
		try {
			endpoint.getConnector().unregisterListener(this, endpoint);
		} catch (AbstractUuahServerException e) {
			throw e;
		} catch (Exception e) {
			throw new ModelException(ServerMessages.failedToUnregister(name,
					endpoint.getEndpointURI()), e);
		}

	}

	protected void startListeners() throws AbstractUuahServerException {
		IUuahEndpoint endpoint = getIncomingEndpoint();
		IMessageReceiver receiver = ((AbstractConnector) endpoint
				.getConnector()).getReceiver(this, endpoint);
		if (receiver != null
				&& endpoint.getConnector().isStarted()
				&& endpoint.getInitialState().equals(
						IUuahEndpoint.INITIAL_STATE_STARTED)) {
			try {
				receiver.start();
			} catch (AbstractUuahException e) {
				throw new AbstractUuahServerException(e);
			}
		}

	}

	protected void stopListeners() throws AbstractUuahServerException {
		IUuahEndpoint endpoint = getIncomingEndpoint();
		IMessageReceiver receiver = ((AbstractConnector) endpoint
				.getConnector()).getReceiver(this, endpoint);
		if (receiver != null) {
			try {
				receiver.stop();
			} catch (AbstractUuahException e) {
				throw new AbstractUuahServerException(e);
			}
		}

	}

	protected void connectListeners() throws AbstractUuahServerException {
		IUuahEndpoint endpoint = getIncomingEndpoint();
		AbstractConnector connector = (AbstractConnector) endpoint
				.getConnector();
		IMessageReceiver receiver = connector.getReceiver(this, endpoint);
		if (receiver != null && connector.isConnected()) {
			try {
				receiver.connect();
			} catch (Exception e) {
				throw new ModelException(MessageFactory
						.createStaticMessage("Failed to connect listener "
								+ receiver + " for endpoint "
								+ endpoint.getName()), e);
			}
		}

	}

	protected void disconnectListeners() throws AbstractUuahServerException {
		IUuahEndpoint endpoint = getIncomingEndpoint();
		IMessageReceiver receiver = ((AbstractConnector) endpoint
				.getConnector()).getReceiver(this, endpoint);
		if (receiver != null) {
			try {
				receiver.disconnect();
			} catch (Exception e) {
				throw new ModelException(MessageFactory
						.createStaticMessage("Failed to disconnect listener "
								+ receiver + " for endpoint "
								+ endpoint.getName()), e);
			}
		}

	}

	protected IUuahEndpoint getIncomingEndpoint() {
		return requestChannel.getEndpoint();
	}

	public void setUuahContext(IUuahContext context) {
		this.uuahContext = context;
	}

	public IModel getModel() {
		return model;
	}

	public void setModel(IModel model) {
		this.model = model;
	}

	public ExceptionListener getExceptionListener() {
		return exceptionListener;
	}

	public void setExceptionListener(ExceptionListener exceptionListener) {
		this.exceptionListener = exceptionListener;
	}

	public IChannel getRequestChannel() {
		return requestChannel;
	}

	public void setRequestChannel(IChannel requestRouter) {
		this.requestChannel = requestRouter;
	}

	public String getInitialState() {
		return initialState;
	}

	public void setInitialState(String initialState) {
		this.initialState = initialState;
	}

	public void setName(String name) {
		this.name = name;
	}

	public IComponent getComponent() {
		return component;
	}

	public void setComponent(IComponent component) {
		this.component = component;
		this.component.setService(this);
	}

	protected IUuahMessage invokeComponent(IUuahEvent event)
			throws AbstractUuahServerException {
		return component.invoke(event);
	}

	public IUuahContext getUuahContext() {
		return uuahContext;
	}

}
